/******************************************************************************
 *                                                                            *
 * Copyright (C) 2024  dyhkwong                                               *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify       *
 * it under the terms of the GNU General Public License as published by       *
 * the Free Software Foundation, either version 3 of the License, or          *
 *  (at your option) any later version.                                       *
 *                                                                            *
 * This program is distributed in the hope that it will be useful,            *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
 * GNU General Public License for more details.                               *
 *                                                                            *
 * You should have received a copy of the GNU General Public License          *
 * along with this program. If not, see <https://www.gnu.org/licenses/>.      *
 *                                                                            *
 ******************************************************************************/

package io.nekohasekai.sagernet.group

import com.github.shadowsocks.plugin.PluginOptions
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import io.nekohasekai.sagernet.fmt.AbstractBean
import io.nekohasekai.sagernet.fmt.anytls.AnyTLSBean
import io.nekohasekai.sagernet.fmt.http.HttpBean
import io.nekohasekai.sagernet.fmt.hysteria2.Hysteria2Bean
import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean
import io.nekohasekai.sagernet.fmt.shadowsocks.supportedShadowsocksMethod
import io.nekohasekai.sagernet.fmt.shadowsocksr.ShadowsocksRBean
import io.nekohasekai.sagernet.fmt.shadowsocksr.supportedShadowsocksRMethod
import io.nekohasekai.sagernet.fmt.shadowsocksr.supportedShadowsocksRObfs
import io.nekohasekai.sagernet.fmt.shadowsocksr.supportedShadowsocksRProtocol
import io.nekohasekai.sagernet.fmt.socks.SOCKSBean
import io.nekohasekai.sagernet.fmt.ssh.SSHBean
import io.nekohasekai.sagernet.fmt.trojan.TrojanBean
import io.nekohasekai.sagernet.fmt.tuic5.Tuic5Bean
import io.nekohasekai.sagernet.fmt.tuic5.supportedTuic5CongestionControl
import io.nekohasekai.sagernet.fmt.tuic5.supportedTuic5RelayMode
import io.nekohasekai.sagernet.fmt.v2ray.VLESSBean
import io.nekohasekai.sagernet.fmt.v2ray.VMessBean
import io.nekohasekai.sagernet.fmt.v2ray.supportedVmessMethod
import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
import io.nekohasekai.sagernet.ktx.*
import kotlin.io.encoding.Base64
import kotlin.time.Duration
import kotlin.time.DurationUnit

fun parseSingBoxOutbound(outbound: JsonObject): List<AbstractBean> {
    when (val type = outbound.getString("type", ignoreCase = false)) {
        "shadowsocks", "trojan", "vmess", "vless", "socks", "http" -> {
            val v2rayBean = when (type) {
                "shadowsocks" -> ShadowsocksBean()
                "trojan" -> TrojanBean()
                "vmess" -> VMessBean()
                "vless" -> VLESSBean()
                "socks" -> SOCKSBean()
                "http" -> HttpBean()
                else -> return listOf()
            }.apply {
                outbound.getString("tag", ignoreCase = false)?.also {
                    name = it
                }
                outbound.getString("server")?.also {
                    serverAddress = it
                } ?: return listOf()
                outbound.getInt("server_port")?.also {
                    serverPort = it
                } ?: return listOf()
            }
            when (type) {
                "trojan", "vmess", "vless" -> {
                    outbound.getObject("transport")?.takeIf { !it.isEmpty }?.also { transport ->
                        when (transport.getString("type")) {
                            "ws" -> {
                                v2rayBean.type = "ws"
                                transport.getString("path")?.also {
                                    v2rayBean.path = it
                                }
                                transport.getObject("headers")?.also { headers ->
                                    headers.getStringArray("host")?.get(0)?.also {
                                        v2rayBean.host = it
                                    } ?: headers.getString("host")?.also {
                                        v2rayBean.host = it
                                    }
                                }
                                transport.getInt("max_early_data")?.also {
                                    v2rayBean.maxEarlyData = it
                                }
                                transport.getString("early_data_header_name")?.also {
                                    v2rayBean.earlyDataHeaderName = it
                                }
                            }
                            "http" -> {
                                v2rayBean.type = "tcp"
                                v2rayBean.headerType = "http"
                                // Difference from v2ray-core
                                // TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used.
                                outbound.getObject("tls")?.also {
                                    if (it.getBoolean("enabled") == true) {
                                        v2rayBean.type = "http"
                                        v2rayBean.headerType = null
                                    }
                                }
                                transport.getString("path")?.also {
                                    v2rayBean.path = it
                                }
                                transport.getStringArray("host")?.also {
                                    v2rayBean.host = it.joinToString("\n")
                                } ?: transport.getString("host")?.also {
                                    v2rayBean.host = it
                                }
                            }
                            "quic" -> {
                                v2rayBean.type = "quic"
                            }
                            "grpc" -> {
                                v2rayBean.type = "grpc"
                                transport.getString("service_name")?.also {
                                    v2rayBean.grpcServiceName = it
                                }
                            }
                            "httpupgrade" -> {
                                v2rayBean.type = "httpupgrade"
                                transport.getString("host")?.also {
                                    v2rayBean.host = it
                                }
                                transport.getString("path")?.also {
                                    v2rayBean.path = it
                                }
                            }
                            else -> return listOf()
                        }
                    }
                }
            }
            when (type) {
                "trojan", "vmess", "vless", "http" -> {
                    outbound.getObject("tls")?.also { tls ->
                        (tls.getBoolean("enabled"))?.also { enabled ->
                            if (enabled) {
                                v2rayBean.security = "tls"
                                tls.getString("server_name")?.also {
                                    v2rayBean.sni = it
                                }
                                tls.getBoolean("insecure")?.also {
                                    v2rayBean.allowInsecure = it
                                }
                                tls.getStringArray("alpn")?.also {
                                    v2rayBean.alpn = it.joinToString("\n")
                                } ?: tls.getString("alpn")?.also {
                                    v2rayBean.alpn = it
                                }
                                if (v2rayBean.alpn == null && v2rayBean.type == "quic") {
                                    // https://github.com/SagerNet/sing-box/pull/1934
                                    v2rayBean.alpn = "h3"
                                }
                                if (!tls.contains("certificate_path")) {
                                    var cert: String? = null
                                    tls.getStringArray("certificate")?.also { certificate ->
                                        cert = certificate.joinToString("\n").takeIf {
                                            it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                                        }
                                    } ?: tls.getString("certificate")?.also { certificate ->
                                        cert = certificate.takeIf {
                                            it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                                        }
                                    }
                                    if (cert != null) {
                                        v2rayBean.certificates = cert
                                    }
                                }
                                if (!tls.contains("client_certificate_path") && !tls.contains("client_key_path")) {
                                    var cert: String? = null
                                    tls.getStringArray("client_certificate")?.also { clientCert ->
                                        cert = clientCert.joinToString("\n").takeIf {
                                            it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                                        }
                                    } ?: tls.getString("client_certificate")?.also { clientCert ->
                                        cert = clientCert.takeIf {
                                            it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                                        }
                                    }
                                    var key: String? = null
                                    tls.getStringArray("client_key")?.also { clientKey ->
                                        key = clientKey.joinToString("\n").takeIf {
                                            it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----")
                                        }
                                    } ?: tls.getString("client_key")?.also { clientKey ->
                                        key = clientKey.takeIf {
                                            it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----")
                                        }
                                    }
                                    if (cert != null && key != null) {
                                        v2rayBean.mtlsCertificate = cert
                                        v2rayBean.mtlsCertificatePrivateKey = key
                                    }
                                }
                                tls.getStringArray("certificate_public_key_sha256")?.also {
                                    v2rayBean.pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n")
                                    v2rayBean.allowInsecure = true
                                } ?: tls.getString("certificate_public_key_sha256")?.also {
                                    v2rayBean.pinnedPeerCertificatePublicKeySha256 = it
                                    v2rayBean.allowInsecure = true
                                }
                                tls.getObject("reality")?.also { reality ->
                                    reality.getBoolean("enabled")?.also { enabled ->
                                        if (enabled) {
                                            v2rayBean.security = "reality"
                                            reality.getString("public_key")?.also {
                                                v2rayBean.realityPublicKey = it
                                            }
                                            reality.getString("short_id")?.also {
                                                v2rayBean.realityShortId = it
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            when (type) {
                "socks" -> {
                    v2rayBean as SOCKSBean
                    outbound.getString("version")?.also {
                        v2rayBean.protocol = when (it) {
                            "4" -> SOCKSBean.PROTOCOL_SOCKS4
                            "4a" -> SOCKSBean.PROTOCOL_SOCKS4A
                            "", "5" -> SOCKSBean.PROTOCOL_SOCKS5
                            else -> return listOf()
                        }
                    }
                    outbound.getString("username")?.also {
                        v2rayBean.username = it
                    }
                    outbound.getString("password")?.also {
                        v2rayBean.password = it
                    }
                }
                "http" -> {
                    v2rayBean as HttpBean
                    outbound.getString("path")?.also {
                        if (it != "" && it != "/") {
                            // unsupported
                            return listOf()
                        }
                    }
                    outbound.getString("username")?.also {
                        v2rayBean.username = it
                    }
                    outbound.getString("password")?.also {
                        v2rayBean.password = it
                    }
                }
                "shadowsocks" -> {
                    v2rayBean as ShadowsocksBean
                    outbound.getString("method")?.also {
                        if (it !in supportedShadowsocksMethod) return listOf()
                        v2rayBean.method = it
                    }
                    outbound.getString("password")?.also {
                        v2rayBean.password = it
                    }
                    outbound.getString("plugin")?.takeIf { it.isNotEmpty() }?.also { pluginId ->
                        if (pluginId != "obfs-local" && pluginId != "v2ray-plugin") return listOf()
                        v2rayBean.plugin = PluginOptions(pluginId, outbound.getString("plugin_opts")).toString(trimId = false)
                    }
                }
                "trojan" -> {
                    v2rayBean as TrojanBean
                    outbound.getString("password")?.also {
                        v2rayBean.password = it
                    }
                }
                "vmess" -> {
                    v2rayBean as VMessBean
                    outbound.getString("uuid")?.also {
                        v2rayBean.uuid = uuidOrGenerate(it)
                    }
                    outbound.getString("security")?.also {
                        if (it !in supportedVmessMethod) return listOf()
                        v2rayBean.encryption = it
                    }
                    outbound.getInt("alter_id")?.also {
                        v2rayBean.alterId = it
                    }
                    outbound.getBoolean("global_padding")?.also {
                        v2rayBean.experimentalAuthenticatedLength = it
                    }
                    v2rayBean.packetEncoding = when (outbound.getString("packet_encoding")) {
                        "packetaddr" -> "packet"
                        "xudp" -> "xudp"
                        else -> "none"
                    }
                }
                "vless" -> {
                    v2rayBean as VLESSBean
                    outbound.getString("uuid")?.also {
                        v2rayBean.uuid = uuidOrGenerate(it)
                    }
                    v2rayBean.packetEncoding = when (outbound.getString("packet_encoding")) {
                        "packetaddr" -> "packet"
                        "xudp", null -> "xudp"
                        else -> "none"
                    }
                    outbound.getString("flow")?.also {
                        when (it) {
                            "" -> {}
                            "xtls-rprx-vision" -> {
                                v2rayBean.flow = "xtls-rprx-vision-udp443"
                                v2rayBean.packetEncoding = "xudp"
                            }
                            else -> return listOf()
                        }
                    }
                }
            }
            return listOf(v2rayBean)
        }
        "hysteria2" -> {
            val hysteria2Bean = Hysteria2Bean().apply {
                outbound.getString("tag", ignoreCase = false)?.also {
                    name = it
                }
                outbound.getString("server")?.also {
                    serverAddress = it
                } ?: return listOf()
                (outbound.getInt("server_port")?.also {
                    serverPorts = it.toString()
                } ?: outbound.getStringArray("server_ports")?.also {
                    serverPorts = it.joinToString(",").replace(":", "-")
                } ?: outbound.getString("server_ports")?.also {
                    serverPorts = it.replace(":", "-")
                }) ?: return listOf()
                if (!serverPorts.isValidHysteriaPort()) {
                    return listOf()
                }
                outbound.getString("hop_interval")?.also { interval ->
                    try {
                        val duration = Duration.parse(interval)
                        hopInterval = duration.toLong(DurationUnit.SECONDS).takeIf { it > 0 }
                    } catch (_: Exception) {}
                }
                outbound.getString("password")?.also {
                    auth = it
                }
                outbound.getObject("tls")?.also { tls ->
                    if (tls.getBoolean("enabled") != true) {
                        return listOf()
                    }
                    if (tls.getObject("reality")?.getBoolean("enabled") == true) {
                        return listOf()
                    }
                    tls.getString("server_name")?.also {
                        sni = it
                    }
                    tls.getBoolean("insecure")?.also {
                        allowInsecure = it
                    }
                    if (!tls.contains("certificate_path")) {
                        var cert: String? = null
                        tls.getStringArray("certificate")?.also { certificate ->
                            cert = certificate.joinToString("\n").takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                            }
                        } ?: tls.getString("certificate")?.also { certificate ->
                            cert = certificate.takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                            }
                        }
                        if (cert != null) {
                            certificates = cert
                        }
                    }
                    if (!tls.contains("client_certificate_path") && !tls.contains("client_key_path")) {
                        var cert: String? = null
                        tls.getStringArray("client_certificate")?.also { clientCert ->
                            cert = clientCert.joinToString("\n").takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                            }
                        } ?: tls.getString("client_certificate")?.also { clientCert ->
                            cert = clientCert.takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                            }
                        }
                        var key: String? = null
                        tls.getStringArray("client_key")?.also { clientKey ->
                            key = clientKey.joinToString("\n").takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----")
                            }
                        } ?: tls.getString("client_key")?.also { clientKey ->
                            key = clientKey.takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----")
                            }
                        }
                        if (cert != null && key != null) {
                            mtlsCertificate = cert
                            mtlsCertificatePrivateKey = key
                        }
                    }
                    tls.getStringArray("certificate_public_key_sha256")?.also {
                        pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n")
                        allowInsecure = true
                    } ?: tls.getString("certificate_public_key_sha256")?.also {
                        pinnedPeerCertificatePublicKeySha256 = it
                        allowInsecure = true
                    }
                } ?: return listOf()
                outbound.getObject("obfs")?.also { obfuscation ->
                    obfuscation.getString("type")?.takeIf { it.isNotEmpty() }?.also { type ->
                        if (type != "salamander") return listOf()
                        obfuscation.getString("password")?.also {
                            obfs = it
                        }
                    }
                }
            }
            return listOf(hysteria2Bean)
        }
        "tuic" -> {
            val tuic5Bean = Tuic5Bean().apply {
                outbound.getString("tag", ignoreCase = false)?.also {
                    name = it
                }
                outbound.getString("server")?.also {
                    serverAddress = it
                } ?: return listOf()
                outbound.getInt("server_port")?.also {
                    serverPort = it
                } ?: return listOf()
                outbound.getString("uuid")?.also {
                    uuid = it
                }
                outbound.getString("password")?.also {
                    password = it
                }
                outbound.getString("congestion_control")?.also {
                    congestionControl = if (it in supportedTuic5CongestionControl) it else "cubic"
                }
                outbound.getString("udp_relay_mode")?.also {
                    udpRelayMode = if (it in supportedTuic5RelayMode) it else "native"
                }
                outbound.getBoolean("zero_rtt_handshake")?.also {
                    zeroRTTHandshake = it
                }
                outbound.getObject("tls")?.also { tls ->
                    if (tls.getBoolean("enabled") != true) {
                        return listOf()
                    }
                    if (tls.getObject("reality")?.getBoolean("enabled") == true) {
                        return listOf()
                    }
                    tls.getString("server_name")?.also {
                        sni = it
                    }
                    tls.getStringArray("alpn")?.also {
                        alpn = it.joinToString("\n")
                    } ?: tls.getString("alpn")?.also {
                        alpn = it
                    }
                    tls.getBoolean("insecure")?.also {
                        allowInsecure = it
                    }
                    tls.getBoolean("disable_sni")?.also {
                        disableSNI = it
                    }
                    if (!tls.contains("certificate_path")) {
                        var cert: String? = null
                        tls.getStringArray("certificate")?.also { certificate ->
                            cert = certificate.joinToString("\n").takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                            }
                        } ?: tls.getString("certificate")?.also { certificate ->
                            cert = certificate.takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                            }
                        }
                        if (cert != null) {
                            certificates = cert
                        }
                    }
                    if (!tls.contains("client_certificate_path") && !tls.contains("client_key_path")) {
                        var cert: String? = null
                        tls.getStringArray("client_certificate")?.also { clientCert ->
                            cert = clientCert.joinToString("\n").takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                            }
                        } ?: tls.getString("client_certificate")?.also { clientCert ->
                            cert = clientCert.takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                            }
                        }
                        var key: String? = null
                        tls.getStringArray("client_key")?.also { clientKey ->
                            key = clientKey.joinToString("\n").takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----")
                            }
                        } ?: tls.getString("client_key")?.also { clientKey ->
                            key = clientKey.takeIf {
                                it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----")
                            }
                        }
                        if (cert != null && key != null) {
                            mtlsCertificate = cert
                            mtlsCertificatePrivateKey = key
                        }
                    }
                    tls.getStringArray("certificate_public_key_sha256")?.also {
                        pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n")
                        allowInsecure = true
                    } ?: tls.getString("certificate_public_key_sha256")?.also {
                        pinnedPeerCertificatePublicKeySha256 = it
                        allowInsecure = true
                    }
                } ?: return listOf()
            }
            return listOf(tuic5Bean)
        }
        "ssh" -> {
            val sshBean = SSHBean().apply {
                outbound.getString("tag", ignoreCase = false)?.also {
                    name = it
                }
                outbound.getString("server")?.also {
                    serverAddress = it
                } ?: return listOf()
                outbound.getInt("server_port")?.also {
                    serverPort = it
                } ?: return listOf()
                outbound.getString("user")?.also {
                    username = it
                }
                if (outbound.getString("password")?.isNotEmpty() == true) {
                    authType = SSHBean.AUTH_TYPE_PASSWORD
                    outbound.getString("password")?.also {
                        password = it
                    }
                }
                if (outbound.getString("private_key")?.isNotEmpty() == true) {
                    authType = SSHBean.AUTH_TYPE_PRIVATE_KEY
                    outbound.getString("private_key")?.also {
                        privateKey = it
                    }
                    outbound.getString("private_key_passphrase")?.also {
                        privateKeyPassphrase = it
                    }
                }
                outbound.getStringArray("host_key")?.also {
                    publicKey = it.joinToString("\n")
                }
            }
            return listOf(sshBean)
        }
        "ssr" -> {
            // removed in v1.6.0
            val ssrBean = ShadowsocksRBean().apply {
                outbound.getString("tag", ignoreCase = false)?.also {
                    name = it
                }
                outbound.getString("server")?.also {
                    serverAddress = it
                } ?: return listOf()
                outbound.getInt("server_port")?.also {
                    serverPort = it
                } ?: return listOf()
                outbound.getString("method")?.also {
                    if (it !in supportedShadowsocksRMethod) return listOf()
                    method = it
                }
                outbound.getString("password")?.also {
                    password = it
                }
                outbound.getString("obfs")?.also {
                    obfs = when (it) {
                        "tls1.2_ticket_fastauth" -> "tls1.2_ticket_auth"
                        in supportedShadowsocksRObfs -> it
                        else -> return listOf()
                    }
                }
                outbound.getString("obfs_param")?.also {
                    obfsParam = it
                }
                outbound.getString("protocol")?.also {
                    if (it !in supportedShadowsocksRProtocol) return listOf()
                    protocol = it
                }
                outbound.getString("protocol_param")?.also {
                    protocolParam = it
                }
            }
            return listOf(ssrBean)
        }
        "anytls" -> {
            val anytlsBean = AnyTLSBean().apply {
                outbound.getString("tag", ignoreCase = false)?.also {
                    name = it
                }
                outbound.getString("server")?.also {
                    serverAddress = it
                } ?: return listOf()
                outbound.getInt("server_port")?.also {
                    serverPort = it
                } ?: return listOf()
                outbound.getObject("tls")?.also { tls ->
                    (tls.getBoolean("enabled"))?.also { enabled ->
                        if (enabled) {
                            security = "tls"
                            tls.getString("server_name")?.also {
                                sni = it
                            }
                            tls.getBoolean("insecure")?.also {
                                allowInsecure = it
                            }
                            tls.getStringArray("alpn")?.also {
                                alpn = it.joinToString("\n")
                            } ?: tls.getString("alpn")?.also {
                                alpn = it
                            }
                            if (!tls.contains("certificate_path")) {
                                var cert: String? = null
                                tls.getStringArray("certificate")?.also { certificate ->
                                    cert = certificate.joinToString("\n").takeIf {
                                        it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                                    }
                                } ?: tls.getString("certificate")?.also { certificate ->
                                    cert = certificate.takeIf {
                                        it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                                    }
                                }
                                if (cert != null) {
                                    certificates = cert
                                }
                            }
                            if (!tls.contains("client_certificate_path") && !tls.contains("client_key_path")) {
                                var cert: String? = null
                                tls.getStringArray("client_certificate")?.also { clientCert ->
                                    cert = clientCert.joinToString("\n").takeIf {
                                        it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                                    }
                                } ?: tls.getString("client_certificate")?.also { clientCert ->
                                    cert = clientCert.takeIf {
                                        it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----")
                                    }
                                }
                                var key: String? = null
                                tls.getStringArray("client_key")?.also { clientKey ->
                                    key = clientKey.joinToString("\n").takeIf {
                                        it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----")
                                    }
                                } ?: tls.getString("client_key")?.also { clientKey ->
                                    key = clientKey.takeIf {
                                        it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----")
                                    }
                                }
                                if (cert != null && key != null) {
                                    mtlsCertificate = cert
                                    mtlsCertificatePrivateKey = key
                                }
                            }
                            tls.getStringArray("certificate_public_key_sha256")?.also {
                                pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n")
                                allowInsecure = true
                            } ?: tls.getString("certificate_public_key_sha256")?.also {
                                pinnedPeerCertificatePublicKeySha256 = it
                                allowInsecure = true
                            }
                            tls.getObject("reality")?.also { reality ->
                                reality.getBoolean("enabled")?.also { enabled ->
                                    if (enabled) {
                                        security = "reality"
                                        reality.getString("public_key")?.also {
                                            realityPublicKey = it
                                        }
                                        reality.getString("short_id")?.also {
                                            realityShortId = it
                                        }
                                    }
                                }
                            }
                        } else {
                            security = "none"
                        }
                    }
                }
            }
            return listOf(anytlsBean)
        }
        "wireguard" -> {
            if (outbound.contains("address")) {
                // wireguard endpoint format introduced in 1.11.0-alpha.19
                return listOf()
            }
            val beanList = mutableListOf<WireGuardBean>()
            val bean = WireGuardBean().apply {
                outbound.getString("tag", ignoreCase = false)?.also {
                    name = it
                }
                outbound.getString("private_key")?.also {
                    privateKey = it
                }
                outbound.getString("peer_public_key")?.also {
                    peerPublicKey = it
                }
                outbound.getString("pre_shared_key")?.also {
                    peerPreSharedKey = it
                }
                mtu = 1408
                outbound.getInt("mtu")?.takeIf { it > 0 }?.also {
                    mtu = it
                }
                outbound.getStringArray("local_address")?.also {
                    localAddress = it.joinToString("\n")
                } ?: outbound.getString("local_address")?.also {
                    localAddress = it
                } ?: return listOf()
                outbound.getIntArray("reserved")?.also {
                    if (it.size == 3) {
                        reserved = listOf(it[0].toString(), it[1].toString(), it[2].toString()).joinToString(",")
                    }
                } ?: outbound.getString("reserved")?.also {
                    val arr = Base64.decode(it)
                    if (arr.size == 3) {
                        reserved = listOf(arr[0].toUByte().toInt().toString(), arr[1].toUByte().toInt().toString(), arr[2].toUByte().toInt().toString()).joinToString(",")
                    }
                }
            }
            if (outbound.contains("server")) {
                outbound.getString("server")?.also {
                    bean.serverAddress = it
                } ?: return listOf()
                outbound.getInt("server_port")?.also {
                    bean.serverPort = it
                } ?: return listOf()
                beanList.add(bean)
            }
            outbound.getArray("peers")?.forEach { peer ->
                beanList.add(bean.applyDefaultValues().clone().apply {
                    peer.getString("server")?.also {
                        serverAddress = it
                    }
                    peer.getInt("server_port")?.also {
                        serverPort = it
                    }
                    peer.getString("public_key")?.also {
                        peerPublicKey = it
                    }
                    peer.getString("pre_shared_key")?.also {
                        peerPreSharedKey = it
                    }
                    peer.getInt("persistent_keepalive_interval")?.takeIf { it > 0 }?.also {
                        keepaliveInterval = it
                    }
                    peer.getIntArray("reserved")?.also {
                        if (it.size == 3) {
                            reserved = listOf(it[0].toString(), it[1].toString(), it[2].toString()).joinToString(",")
                        }
                    } ?: peer.getString("reserved")?.also {
                        val arr = Base64.decode(it)
                        if (arr.size == 3) {
                            reserved = listOf(arr[0].toUByte().toInt().toString(), arr[1].toUByte().toInt().toString(), arr[2].toUByte().toInt().toString()).joinToString(",")
                        }
                    }
                })
            }
            return beanList
        }
        else -> return listOf()
    }
}

fun parseSingBoxEndpoint(endpoint: JsonObject): List<AbstractBean> {
    when (endpoint.getString("type", ignoreCase = false)) {
        "wireguard" -> {
            val beanList = mutableListOf<WireGuardBean>()
            if (endpoint.contains("local_address")) {
                // legacy wireguard outbound format
                return listOf()
            }
            val bean = WireGuardBean().apply {
                endpoint.getString("tag", ignoreCase = false)?.also {
                    name = it
                }
                endpoint.getString("private_key")?.also {
                    privateKey = it
                }
                mtu = 1408
                endpoint.getInt("mtu")?.takeIf { it > 0 }?.also {
                    mtu = it
                }
                endpoint.getStringArray("address")?.also {
                    localAddress = it.joinToString("\n")
                } ?: endpoint.getString("address")?.also {
                    localAddress = it
                } ?: return listOf()
            }
            endpoint.getArray("peers")?.forEach { peer ->
                beanList.add(bean.applyDefaultValues().clone().apply {
                    peer.getString("address")?.also {
                        serverAddress = it
                    }
                    peer.getInt("port")?.also {
                        serverPort = it
                    }
                    peer.getString("public_key")?.also {
                        peerPublicKey = it
                    }
                    peer.getString("pre_shared_key")?.also {
                        peerPreSharedKey = it
                    }
                    peer.getInt("persistent_keepalive_interval")?.takeIf { it > 0 }?.also {
                        keepaliveInterval = it
                    }
                    peer.getIntArray("reserved")?.also {
                        if (it.size == 3) {
                            reserved = listOf(it[0].toString(), it[1].toString(), it[2].toString()).joinToString(",")
                        }
                    } ?: peer.getString("reserved")?.also {
                        val arr = Base64.decode(it)
                        if (arr.size == 3) {
                            reserved = listOf(arr[0].toUByte().toInt().toString(), arr[1].toUByte().toInt().toString(), arr[2].toUByte().toInt().toString()).joinToString(",")
                        }
                    }
                })
            }
            return beanList
        }
        else -> return listOf()
    }
}

private fun JsonObject.contains(key: String, ignoreCase: Boolean = true): Boolean {
    if (has(key)) {
        return true
    }
    if (ignoreCase) {
        for (it in keySet()) {
            if (it.equals(key, ignoreCase = true)) {
                return true
            }
        }
    }
    return false
}

private fun JsonObject.getString(key: String, ignoreCase: Boolean = true): String? {
    if (has(key)) {
        return try {
            get(key).asString
        } catch (_: Exception) {
            null
        }
    }
    if (ignoreCase) {
        for (it in keySet()) {
            if (it.equals(key, ignoreCase = true)) {
                return try {
                    get(it).asString
                } catch (_: Exception) {
                    null
                }
            }
        }
    }
    return null
}

private fun JsonObject.getInt(key: String, ignoreCase: Boolean = true): Int? {
    if (has(key)) {
        return try {
            get(key).asInt
        } catch (_: Exception) {
            null
        }
    }
    if (ignoreCase) {
        for (it in keySet()) {
            if (it.equals(key, ignoreCase = true)) {
                return try {
                    get(it).asInt
                } catch (_: Exception) {
                    null
                }
            }
        }
    }
    return null
}

private fun JsonObject.getBoolean(key: String, ignoreCase: Boolean = true): Boolean? {
    if (has(key)) {
        return try {
            get(key).asBoolean
        } catch (_: Exception) {
            null
        }
    }
    if (ignoreCase) {
        for (it in keySet()) {
            if (it.equals(key, ignoreCase = true)) {
                return try {
                    get(it).asBoolean
                } catch (_: Exception) {
                    null
                }
            }
        }
    }
    return null
}

private fun JsonObject.getObject(key: String, ignoreCase: Boolean = true): JsonObject? {
    if (has(key)) {
        return try {
            get(key).asJsonObject
        } catch (_: Exception) {
            null
        }
    }
    if (ignoreCase) {
        for (it in keySet()) {
            if (it.equals(key, ignoreCase = true)) {
                return try {
                    get(it).asJsonObject
                } catch (_: Exception) {
                    null
                }
            }
        }
    }
    return null
}

private fun JsonObject.getJsonArray(key: String, ignoreCase: Boolean = true): JsonArray? {
    if (has(key)) {
        return try {
            get(key).asJsonArray
        } catch (_: Exception) {
            null
        }
    }
    if (ignoreCase) {
        for (it in keySet()) {
            if (it.equals(key, ignoreCase = true)) {
                return try {
                    get(it).asJsonArray
                } catch (_: Exception) {
                    null
                }
            }
        }
    }
    return null
}

private fun JsonObject.getArray(key: String, ignoreCase: Boolean = true): List<JsonObject>? {
    val array = getJsonArray(key, ignoreCase)?.asList() ?: return null
    val list = mutableListOf<JsonObject>()
    try {
        for (obj in array) {
            list.add(obj.asJsonObject)
        }
    } catch (_: Exception) {
        return null
    }
    return list
}

private fun JsonObject.getStringArray(key: String, ignoreCase: Boolean = true): List<String>? {
    val array = getJsonArray(key, ignoreCase)?.asList() ?: return null
    val list = mutableListOf<String>()
    try {
        for (obj in array) {
            list.add(obj.asString)
        }
    } catch (_: Exception) {
        return null
    }
    return list
}

private fun JsonObject.getIntArray(key: String, ignoreCase: Boolean = true): List<Int>? {
    val array = getJsonArray(key, ignoreCase)?.asList() ?: return null
    val list = mutableListOf<Int>()
    try {
        for (obj in array) {
            list.add(obj.asInt)
        }
    } catch (_: Exception) {
        return null
    }
    return list
}
